/*
 * TinyFSM - Tiny Finite State Machine Processor
 *
 * Copyright (c) 2012-2022 Axel Burri
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/* ---------------------------------------------------------------------
 * Version: 0.3.3
 *
 * API documentation: see "../doc/50-API.md"
 *
 * The official TinyFSM website is located at:
 * https://digint.ch/tinyfsm/
 *
 * Author:
 * Axel Burri <axel@tty0.ch>
 * ---------------------------------------------------------------------
 */

#ifndef TINYFSM_HPP_INCLUDED
#define TINYFSM_HPP_INCLUDED

#ifndef TINYFSM_NOSTDLIB
#include <type_traits>
#endif

// #include <iostream>
// #define DBG(str) do { std::cerr << str << std::endl; } while( false )
// DBG("*** dbg_example *** " << __PRETTY_FUNCTION__);

namespace tinyfsm
{

// --------------------------------------------------------------------------

struct Event
{
};

// --------------------------------------------------------------------------

#ifdef TINYFSM_NOSTDLIB
// remove dependency on standard library (silent fail!).
// useful in conjunction with -nostdlib option, e.g. if your compiler
// does not provide a standard library.
// NOTE: this silently disables all static_assert() calls below!
template <typename F, typename S>
struct is_same_fsm
{
    static constexpr bool value = true;
};
#else
// check if both fsm and state class share same fsmtype
template <typename F, typename S>
struct is_same_fsm : std::is_same<typename F::fsmtype, typename S::fsmtype>
{
};
#endif

template <typename S>
struct _state_instance
{
    using value_type = S;
    using type = _state_instance<S>;
    static S value;
};

template <typename S>
typename _state_instance<S>::value_type _state_instance<S>::value;

// --------------------------------------------------------------------------

template <typename F>
class Fsm
{
public:
    using fsmtype = Fsm<F>;
    using state_ptr_t = F *;

    static state_ptr_t current_state_ptr;

    // public, leaving ability to access state instance (e.g. on reset)
    template <typename S>
    static constexpr S &state(void)
    {
        static_assert(is_same_fsm<F, S>::value, "accessing state of different state machine");
        return _state_instance<S>::value;
    }

    template <typename S>
    static constexpr bool is_in_state(void)
    {
        static_assert(is_same_fsm<F, S>::value, "accessing state of different state machine");
        return current_state_ptr == &_state_instance<S>::value;
    }

    /// state machine functions
public:
    // explicitely specialized in FSM_INITIAL_STATE macro
    static void set_initial_state();

    static void reset() {};

    static void enter()
    {
        current_state_ptr->entry();
    }

    static void start()
    {
        set_initial_state();
        enter();
    }

    template <typename E>
    static void dispatch(E const &event)
    {
        current_state_ptr->react(event);
    }

    /// state transition functions
protected:
    template <typename S>
    void transit(void)
    {
        static_assert(is_same_fsm<F, S>::value, "transit to different state machine");
        current_state_ptr->exit();
        current_state_ptr = &_state_instance<S>::value;
        current_state_ptr->entry();
    }

    template <typename S, typename ActionFunction>
    void transit(ActionFunction action_function)
    {
        static_assert(is_same_fsm<F, S>::value, "transit to different state machine");
        current_state_ptr->exit();
        // NOTE: do not send events in action_function definisions.
        action_function();
        current_state_ptr = &_state_instance<S>::value;
        current_state_ptr->entry();
    }

    template <typename S, typename ActionFunction, typename ConditionFunction>
    void transit(ActionFunction action_function, ConditionFunction condition_function)
    {
        if (condition_function())
        {
            transit<S>(action_function);
        }
    }
};

template <typename F>
typename Fsm<F>::state_ptr_t Fsm<F>::current_state_ptr;

// --------------------------------------------------------------------------

template <typename... FF>
struct FsmList;

template <>
struct FsmList<>
{
    static void set_initial_state() {}
    static void reset() {}
    static void enter() {}
    template <typename E>
    static void dispatch(E const &)
    {
    }
};

template <typename F, typename... FF>
struct FsmList<F, FF...>
{
    using fsmtype = Fsm<F>;

    static void set_initial_state()
    {
        fsmtype::set_initial_state();
        FsmList<FF...>::set_initial_state();
    }

    static void reset()
    {
        F::reset();
        FsmList<FF...>::reset();
    }

    static void enter()
    {
        fsmtype::enter();
        FsmList<FF...>::enter();
    }

    static void start()
    {
        set_initial_state();
        enter();
    }

    template <typename E>
    static void dispatch(E const &event)
    {
        fsmtype::template dispatch<E>(event);
        FsmList<FF...>::template dispatch<E>(event);
    }
};

// --------------------------------------------------------------------------

template <typename... SS>
struct StateList;
template <>
struct StateList<>
{
    static void reset() {}
};
template <typename S, typename... SS>
struct StateList<S, SS...>
{
    static void reset()
    {
        _state_instance<S>::value = S();
        StateList<SS...>::reset();
    }
};

// --------------------------------------------------------------------------

template <typename F>
struct MooreMachine : tinyfsm::Fsm<F>
{
    virtual void entry(void) {}; /* entry actions in some states */
    void exit(void) {};          /* no exit actions */
};

template <typename F>
struct MealyMachine : tinyfsm::Fsm<F>
{
    // input actions are modeled in react():
    // - conditional dependent of event type or payload
    // - transit<>(ActionFunction)
    void entry(void) {}; /* no entry actions */
    void exit(void) {};  /* no exit actions */
};

} /* namespace tinyfsm */

#define FSM_INITIAL_STATE(_FSM, _STATE)                      \
    namespace tinyfsm                                        \
    {                                                        \
    template <>                                              \
    void Fsm<_FSM>::set_initial_state(void)                  \
    {                                                        \
        current_state_ptr = &_state_instance<_STATE>::value; \
    }                                                        \
    }

#endif /* TINYFSM_HPP_INCLUDED */
